﻿using System;
using System.Collections.Generic;
using System.Reflection;

namespace Rsdn.SmartApp
{
	/// <summary>
	/// Вспомогательный класс для создания экземпляров.
	/// </summary>
	public static class InstancingHelper
	{
		/// <summary>
		/// Создать экземпляр указанного типа.
		/// </summary>
		public static T CreateInstance<T>(IServiceProvider provider,
			params InstancingCustomParam[] customParams)
		{
			return (T)typeof (T).CreateInstance(provider, customParams);
		}

		/// <summary>
		/// Создать экземпляр указанного типа.
		/// </summary>
		public static object CreateInstance(this Type type, IServiceProvider provider,
			params InstancingCustomParam[] customParams)
		{
			var customParamsMap = new Dictionary<Type, object>(customParams.Length);
			foreach (var p in customParams)
			{
				if (customParamsMap.ContainsKey(p.ParameterType))
					throw new ArgumentException("Duplicate parameters with type '"
						+ p.ParameterType.FullName + "'.");
				customParamsMap.Add(p.ParameterType, p.Value);
			}

			var ctor = FindCtor(type);
			var ctorParamValues = new List<object>();
			var ctorParams = ctor.GetParameters();
			var cpStartFrom = 0;
			if (ctorParams.Length > 0)
			{
				if (ctorParams[0].ParameterType == typeof(IServiceProvider))
				{
					ctorParamValues.Add(provider);
					cpStartFrom++;
				}
				for (int i = cpStartFrom; i < ctorParams.Length; i++)
				{
					object val;
					if (!customParamsMap.TryGetValue(ctorParams[i].ParameterType, out val))
						throw new ArgumentException("Parameter '" + ctorParams[i].Name + "' not supplied");
					ctorParamValues.Add(val);
				}
			}
			try
			{
				return ctor.Invoke(ctorParamValues.ToArray());
			}
			catch (TargetInvocationException ex)
			{
				throw ex.InnerException;
			}
		}

		private static ConstructorInfo FindCtor(Type type)
		{
			var ctors = type.GetConstructors();
			if (ctors.Length == 0)
				throw new ArgumentException("Type '" + type.FullName
					+ "' contains no public constructors.");
			if (ctors.Length == 1)
				return ctors[0];
			else
			{
				ConstructorInfo curCtor = null;
				foreach (var ctor in ctors)
				{
					if (Attribute.IsDefined(ctor, typeof (DefaultConstructorAttribute)))
					{
						if (curCtor != null)
							throw new ArgumentException("Only one constructor can be marked by '"
								+ typeof(DefaultConstructorAttribute).FullName + "' attribute.");
						curCtor = ctor;
					}
				}
				if (curCtor == null)
					throw new ArgumentException("No default constructor found.");
				return curCtor;
			}
		}
	}
}
